import { _decorator, instantiate, Intersection2D, Node, Prefab, size, Toggle, UITransform, v2 } from 'cc';

import BaseView from '../../../../../../extensions/app/assets/base/BaseView';
import { Body, SAP } from '../../../../../pkg-export/@gamex/cc-sap';
import { checkCollision, Circle, Polygon, Rect, Shape } from '../../../../../pkg-export/@gamex/cc-sat';
import { Collider } from './expansion/Collider';

type IBody = Body<Collider>;
type IShape = Shape;

declare module 'cc' {
    interface Node {
        body: IBody;
        shape: IShape;
        speed: number;
        speedX: number;
        speedY: number;
        speedS: number;
    }
}

const { ccclass, property } = _decorator;
@ccclass('PageCollision')
export class PageCollision extends BaseView {
    @property(Node)
    public container: Node = null;

    @property(Prefab)
    public rectPrefab: Prefab = null;

    @property(Prefab)
    public circlePrefab: Prefab = null;

    @property(Prefab)
    public sanjiaoPrefab: Prefab = null;

    @property(Toggle)
    public openSap: Toggle = null;

    @property(Toggle)
    public openSat: Toggle = null;

    @property(Toggle)
    public openRun: Toggle = null;

    @property(Toggle)
    public openScale: Toggle = null;

    @property(Toggle)
    public openRotate: Toggle = null;

    private sap = new SAP<Collider>();

    private winSize = size(900, 600);

    private createLine(count: number) {
        for (let index = 0; index < count; index++) {
            const node = instantiate(this.rectPrefab);
            node.parent = this.container;
            node.getComponent(UITransform).setContentSize(200, 4);
            node.setPosition(Math.random() * this.winSize.width - this.winSize.width / 2, Math.random() * this.winSize.height - this.winSize.height / 2);
            const box = node.getComponent(UITransform).getBoundingBox();
            const angle = Math.floor(Math.random() * 180);

            node['shape'] = new Polygon([
                v2(box.xMin, box.yMin),
                v2(box.xMin, box.yMax),
                v2(box.xMax, box.yMax),
                v2(box.xMax, box.yMin),
            ]).rotate(angle);
            node.angle = angle;

            node['speed'] = 20 + Math.floor(Math.random() * 80);
            node['speedX'] = Math.random() > 0.5 ? 1 : -1;
            node['speedY'] = Math.random() > 0.5 ? 1 : -1;
            node['speedS'] = Math.random() > 0.5 ? 1 : -1;

            node['body'] = new Body();
            node['body'].setAABB(node['shape'].aabb);
            node['body'].setData(node.getComponent(Collider));

            this.sap.insert(node['body']);
        }
    }

    private createRect(count: number) {
        for (let index = 0; index < count; index++) {
            const node = instantiate(this.rectPrefab);
            node.parent = this.container;
            node.setPosition(Math.random() * this.winSize.width - this.winSize.width / 2, Math.random() * this.winSize.height - this.winSize.height / 2);
            const box = node.getComponent(UITransform).getBoundingBox();

            node['shape'] = new Rect(box);
            node['speed'] = 20 + Math.floor(Math.random() * 80);
            node['speedX'] = Math.random() > 0.5 ? 1 : -1;
            node['speedY'] = Math.random() > 0.5 ? 1 : -1;
            node['speedS'] = Math.random() > 0.5 ? 1 : -1;

            node['body'] = new Body();
            node['body'].setAABB(node['shape'].aabb);
            node['body'].setData(node.getComponent(Collider));

            this.sap.insert(node['body']);
        }
    }

    private createSanjiao(count: number) {
        for (let index = 0; index < count; index++) {
            const node = instantiate(this.sanjiaoPrefab);
            node.parent = this.container;
            node.setPosition(Math.random() * this.winSize.width - this.winSize.width / 2, Math.random() * this.winSize.height - this.winSize.height / 2);
            const box = node.getComponent(UITransform).getBoundingBox();

            node['shape'] = new Polygon([
                v2(box.xMin, box.yMin),
                v2(box.xMin, box.yMax),
                v2(box.xMax, box.center.y)
            ], node.position).rotate(node.angle);
            node['speed'] = 20 + Math.floor(Math.random() * 80);
            node['speedX'] = Math.random() > 0.5 ? 1 : -1;
            node['speedY'] = Math.random() > 0.5 ? 1 : -1;
            node['speedS'] = Math.random() > 0.5 ? 1 : -1;

            node['body'] = new Body();
            node['body'].setAABB(node['shape'].aabb);
            node['body'].setData(node.getComponent(Collider));

            this.sap.insert(node['body']);
        }
    }

    private createCircle(count: number) {
        for (let index = 0; index < count; index++) {
            const node = instantiate(this.circlePrefab);
            node.parent = this.container;
            node.setPosition(Math.random() * this.winSize.width - this.winSize.width / 2, Math.random() * this.winSize.height - this.winSize.height / 2);
            const box = node.getComponent(UITransform).getBoundingBox();

            node['shape'] = new Circle(box.width / 2, box.center);
            node['speed'] = 20 + Math.floor(Math.random() * 80);
            node['speedX'] = Math.random() > 0.5 ? 1 : -1;
            node['speedY'] = Math.random() > 0.5 ? 1 : -1;
            node['speedS'] = Math.random() > 0.5 ? 1 : -1;

            node['body'] = new Body();
            node['body'].setAABB(node['shape'].aabb);
            node['body'].setData(node.getComponent(Collider));

            this.sap.insert(node['body']);
        }
    }

    start() {
        this.createLine(10);
        this.createRect(330);
        this.createSanjiao(330);
        this.createCircle(330);
    }

    private broadPhaseCallback(a: IBody, b: IBody) {
        if (this.checkCollisionNarrowPhase(a.data.node, b.data.node)) {
            a.data.onTrigger();
            b.data.onTrigger();
        }
    }

    // 粗碰撞检测
    checkCollisionBroadPhase() {
        if (this.openSap.isChecked) {
            this.sap.trigger(this.broadPhaseCallback, this);
        } else {
            const children = this.container.children;
            for (let i = 0; i < children.length; i++) {
                const childA = children[i];
                for (let j = i + 1; j < children.length; j++) {
                    const childB = children[j];
                    if (this.checkCollisionNarrowPhase(childA, childB)) {
                        childA.getComponent(Collider).onTrigger();
                        childB.getComponent(Collider).onTrigger();
                    }
                }
            }
        }
    }

    // 细碰撞检测
    checkCollisionNarrowPhase(nodeA: Node, nodeB: Node) {
        const sp1 = nodeA['shape'] as Shape;
        const sp2 = nodeB['shape'] as Shape;
        if (this.openSat.isChecked) {
            return checkCollision(sp1, sp2);
        } else {
            if (sp1 instanceof Circle && sp2 instanceof Circle) {
                return Intersection2D.circleCircle(
                    sp1.center, sp1.radius,
                    sp2.center, sp2.radius
                );
            }
            if (sp1 instanceof Circle) {
                return Intersection2D.polygonCircle(
                    sp2.vertices,
                    sp1.center, sp1.radius
                );
            }

            if (sp2 instanceof Circle) {
                return Intersection2D.polygonCircle(
                    sp1.vertices,
                    sp2.center, sp2.radius
                );
            }

            return Intersection2D.polygonPolygon(sp1.vertices, sp2.vertices);
        }
    }

    update(dt: number) {
        const children = this.container.children;

        // 移动、旋转、缩放
        for (let i = 0; i < children.length; i++) {
            const child = children[i];
            const shape = child['shape'];

            // 移动
            if (this.openRun.isChecked) {
                const addX = child['speed'] * dt * child['speedX'];
                const addY = child['speed'] * dt * child['speedY'];
                shape.move(addX, addY);
                child.setPosition(child.position.x + addX, child.position.y + addY);

                if (child.position.x > this.winSize.width / 2) {
                    child['speedX'] = -1;
                } else if (child.position.x < -this.winSize.width / 2) {
                    child['speedX'] = 1;
                }
                if (child.position.y > this.winSize.height / 2) {
                    child['speedY'] = -1;
                } else if (child.position.y < -this.winSize.height / 2) {
                    child['speedY'] = 1;
                }
            }

            // 旋转
            if (this.openRotate.isChecked) {
                if (shape instanceof Polygon) {
                    shape.rotate(-100 * dt);
                    child.angle += -100 * dt;
                }
            }

            // 缩放
            if (this.openScale.isChecked) {
                const diff = 0.5 * dt * child['speedS'];
                shape.scale(diff / child.scale.x);
                child.setScale(child.scale.x + diff, child.scale.y + diff, 1);

                if (child.scale.x > 2 || child.scale.x < 1) {
                    child['speedS'] *= -1;
                }
            }

            // 更新SAP
            if (this.openSap.isChecked) {
                child.body.setAABB(shape.aabb);
            }
        }

        // 碰撞检测
        this.checkCollisionBroadPhase();
    }
}